package com.allwees.bs.c.module.ops.service.impl;

import com.allwees.bs.c.module.ops.entity.PointActiveDefEntity;
import com.allwees.bs.c.module.ops.repository.PointActiveDefRepository;
import com.allwees.bs.c.module.ops.service.PointActiveDefUseService;
import com.allwees.bs.c.module.user.dao.UserPointDao;
import com.allwees.bs.c.module.user.entity.UserPointEntity;
import com.allwees.bs.c.module.user.entity.UserPointLogEntity;
import com.allwees.bs.c.module.user.repository.UserPointLogRepository;
import com.allwees.bs.core.model.coupon.constant.PointChangeTypeEnum;
import com.allwees.core.common.constant.EDelete;
import com.allwees.core.common.jexl.JexlEngine;
import com.allwees.core.common.util.MoneyUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class PointActiveDefUseServiceImpl implements PointActiveDefUseService {

    @Autowired
    private PointActiveDefRepository pointActiveDefRepository;

    @Resource
    private UserPointDao userPointDao;

    @Autowired
    private UserPointLogRepository userPointLogRepository;


    /**
     * 使用积分
     *
     * @param userUuid
     * @param useAmount
     * @param useType
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void use(String userUuid, BigDecimal useAmount, PointChangeTypeEnum useType) {
        if (StringUtils.isBlank(userUuid)) {
            log.warn(">>>anonymous user use point.");
            return;
        }
        UserPointEntity userPoint = userPointDao.findOneByField("user_uuid",userUuid);
        if (userPoint == null) {
            log.warn(">>>user use point with a not exsited userUuid:{}.",userUuid);
            return;
        }

        Assert.isTrue(userPoint.getValue().compareTo(useAmount) > 0, "you point not enough");
        userPoint.setValue(MoneyUtil.sub(userPoint.getValue(), useAmount));
        userPointDao.updateById(userPoint);
        saveLog(userUuid, userUuid, null, useAmount, useType);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void registered(String userUuid) {
        if (StringUtils.isBlank(userUuid)) {
            log.warn(">>>anonymous user call registered...");
            return;
        }
        receive(userUuid, PointChangeTypeEnum.SYSTEM_REGISTERED, null, userUuid);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void orderReviewed(String userUuid, String condition, String orderUuid) {
        if (StringUtils.isBlank(userUuid)) {
            log.warn(">>>anonymous user call order reviewed...");
            return;
        }
        receive(userUuid, PointChangeTypeEnum.SYSTEM_REVIEW, condition, orderUuid);
    }

    private void receive(String userUuid, PointChangeTypeEnum changeType, String condition, String logKey) {
        log.info(">>>user receive point,[userUuid:{},useType:{},condition:{},logKey:{}]",userUuid,changeType,condition,logKey);
        if (isReceive(userUuid, logKey, changeType)) {
            log.info(">>>user already received,userUuid:{}",userUuid);
            return;
        }

        List<PointActiveDefEntity> defs = pointActiveDefRepository.findByUseType(changeType);
        //积分活动规则未定义
        if (CollectionUtils.isEmpty(defs)) {
            return;
        }
        for (PointActiveDefEntity def : defs) {
            receive(userUuid,logKey, def, condition,changeType);
        }
    }

    private void receive(String userUuid, String logKey, PointActiveDefEntity def, String condition, PointChangeTypeEnum useType) {
        UserPointEntity userPoint = userPointDao.findOneByField("user_uuid",userUuid);
        if (userPoint == null) {
            userPoint = new UserPointEntity();
            userPoint.setUserUuid(userUuid);
            userPoint.setValue(BigDecimal.ZERO);
            userPointDao.create(userPoint);
        }

        //附加
        BigDecimal rewardValue = def.getValue();
        BigDecimal conditionValue = evaluate(def.getConditionExpression(), condition, def.getConditionValue());
        BigDecimal receiveValue = MoneyUtil.add(rewardValue, conditionValue);
        userPoint.setValue(MoneyUtil.add(receiveValue,userPoint.getValue()));
        userPointDao.updateById(userPoint);
        //积分流水
        saveLog(userUuid, logKey, def.getUuid(), receiveValue, useType);
    }

    private boolean isReceive(String userUuid, String logKey, PointChangeTypeEnum useType) {
        UserPointLogEntity entity = new UserPointLogEntity();
        entity.setUserUuid(userUuid);
        entity.setLogKey(logKey);
        entity.setUseType(useType);
        entity.setIsDeleted(EDelete.UNDELETED.value());
        return userPointLogRepository.exists(Example.of(entity));
    }

    private static BigDecimal evaluate(String expression, String condition, BigDecimal conditionValue) {
        if (StringUtils.isBlank(expression) || conditionValue == null || StringUtils.isBlank(condition)) {
            return BigDecimal.ZERO;
        }

        Map<String, Object> map = new HashMap<>(3);
        map.put("conditionValue", conditionValue);
        map.put("condition", condition);
        map.put("defaultValue", BigDecimal.ZERO);

        return (BigDecimal) JexlEngine.execute(expression, map);
    }

    /**
     * 保存积分变更日志
     *
     * @param userUuid
     * @param logKey
     * @param pointUuid
     * @param value
     * @param useType
     */
    @Override
    public void saveLog(String userUuid, String logKey, String pointUuid, BigDecimal value, PointChangeTypeEnum useType) {
        UserPointLogEntity log = new UserPointLogEntity();
        log.setUserUuid(userUuid);
        log.setPointUuid(pointUuid);
        log.setValue(value);
        log.setUseType(useType);
        log.setLogKey(logKey);
        userPointLogRepository.save(log);
    }
}
